/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core;

import com.blandware.atleap.common.Constants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.taglib.TagUtils;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;


/**
 * <p>This class is designed to put all the public static variables (or only one
 * of them) in a class to a specified scope - designed for exporting constants
 * to Tag Libraries.
 * </p>
 * <p>In order to prevent <code>java.lang.ClassCastException</code> to be thrown during execution,
 * all variables are cast to <code>java.lang.String</code> using String.valueOf() method
 * before being exported to specified scope.<br />
 * </p>
 * <p>It is designed to be used as follows:
 * <pre>&lt;atleap:constants className="fully.qualyfied.ClassName" /&gt;</pre>
 * or
 * <pre>&lt;atleap:constants className="fully.qualyfied.ClassName" var="SPECIFIC_CONST" /&gt;</pre>
 * </p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>className</b> - name of class from which to export constants (this should
 * be fully-qualified). If not specified, constants are exported from
 * <code>com.blandware.atleap.common.Constants</code>.
 * </li>
 * <li>
 * <b>var</b> - if specified, only constant with given name will be exported.
 * Otherwise, all constants will be exported.
 * </li>
 * <li>
 * <b>scope</b> - scope to which value(s) will be exported
 * </li>
 * </ul>
 * </p>
 * <p><a href="ConstantsTag.java.html"><i>View Source</i></a>
 * </p>
 *
 * @author Matt Raible <a href="mailto:matt@raibledesigns.com">&lt;matt@raibledesigns.com&gt;</a>
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.14 $ $Date: 2007/12/18 11:43:38 $
 * @jsp.tag name="constants" body-content="empty"
 * @see java.lang.ClassCastException
 * @see java.lang.String#valueOf(java.lang.Object)
 */
public class ConstantsTag extends SimpleTagSupport {
	protected transient final Log log = LogFactory.getLog(ConstantsTag.class);

	/**
	 * The class to expose the variables from.
	 */
	public String className = Constants.class.getName();

	/**
	 * The scope to put the variable in.
	 */
	protected String scope = null;

	/**
	 * The single variable to expose.
	 */
	protected String var = null;

    /**
     * Processes the tag
     *
     * @throws JspException
     */
	public void doTag() throws JspException {

		PageContext pageContext = (PageContext) getJspContext();

		// Using reflection, get the available field names in the class
		Class c;
		Object obj = null;
		int toScope = PageContext.PAGE_SCOPE;

		if ( scope != null ) {
			// default to pageScope
			toScope = TagUtils.getInstance().getScope(scope);
		}

		try {
			c = Class.forName(className);
		} catch ( ClassNotFoundException cnf ) {
			if ( log.isErrorEnabled() ) {
				log.error("Class " + className + " not found - maybe a typo?");
			}
			throw new JspTagException("Class not found: " + cnf.getMessage());
		}

		try {
			// if var is null, expose all variables
			if ( var == null ) {

				// only public static fields will be exported

				Field[] fields = c.getFields();

				AccessibleObject.setAccessible(fields, true);

				for ( int i = 0; i < fields.length; i++ ) {
					if ( Modifier.isStatic(fields[i].getModifiers()) ) {
						if ( log.isDebugEnabled() ) {
							log.debug("putting '" + fields[i].getName() + "=" +
							        fields[i].get(obj) + "' into " + scope +
							        " scope");
						}
						pageContext.setAttribute(fields[i].getName(),
						        String.valueOf(fields[i].get(obj)), toScope);
					}
				}

			} else {
				try {
					Field field = c.getField(var);
					if ( Modifier.isStatic(field.getModifiers()) ) {
						String value = String.valueOf(field.get(null));
						pageContext.setAttribute(field.getName(), value,
						        toScope);
					} else {
						String errorMessage = "Field '" + var + "' is not static and will not be exported";
						if ( log.isDebugEnabled() ) {
							log.debug(errorMessage);
						}
						throw new JspTagException(errorMessage);
					}
				} catch ( NoSuchFieldException nsf ) {
					log.error(nsf.getMessage());
					throw new JspTagException(nsf);
				}
			}
		} catch ( IllegalAccessException iae ) {
			log.error("Illegal Access Exception - maybe a classloader issue?");
			throw new JspTagException(iae);
		}

		if ( log.isDebugEnabled() ) {
			log.debug("Continue processing this page");
		}

	}

	/**
     * Sets name of class to expose constants from
     *
     * @param clazz name of class to set
	 * @jsp.attribute
	 */
	public void setClassName(String clazz) {
		this.className = clazz;
	}

    /**
     * Returns name of class to expose constants from
     *
     * @return name of class
     */
	public String getClassName() {
		return this.className;
	}

	/**
     * Sets scope to which to expose constants
     *
     * @param scope scope to set
	 * @jsp.attribute
	 */
	public void setScope(String scope) {
		this.scope = scope;
	}

    /**
     * Returns scope to which to expose constants
     *
     * @return scope
     */
	public String getScope() {
		return (this.scope);
	}

	/**
     * Sets name of the concrete constant to expose
     *
     * @param var name of constant
	 * @jsp.attribute
	 */
	public void setVar(String var) {
		this.var = var;
	}

    /**
     * Returns name of the concrete constant to expose
     *
     * @return name of constant
     */
	public String getVar() {
		return (this.var);
	}

}
